
#include "Common.hpp"
#include <sstream>
#include <algorithm>
using namespace std;

//-----------------------------------------------------------------------------
bool ProcessCommonEvent(const SDL_Event& evt) {
    switch (evt.type) {
    case SDL_WINDOWEVENT:
        if (evt.window.event == SDL_WINDOWEVENT_CLOSE) { // quit
            SDL_Event evt = { 0 };
            evt.type = SDL_QUIT;
            SDL_PushEvent(&evt);
            return true;
        }
        break;

    case SDL_KEYDOWN:
        if (evt.key.keysym.sym == SDLK_ESCAPE) { // quit
            SDL_Event evt = { 0 };
            evt.type = SDL_QUIT;
            SDL_PushEvent(&evt);
            return true;
        }
        else if (evt.key.keysym.sym == SDLK_F12) { // make a screenshot
            SDL_Window* window = SDL_GetWindowFromID(evt.key.windowID);
            if (window) {
                static int number = 0;
                ostringstream ss;
                ss << "screenshot" << number++ << ".png";

                int w, h;
                SDL_GetWindowSize(window, &w, &h);
                const int stride = w * 3;
                vector<unsigned char> fb(h * stride);
                glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, &fb[0]);

                vector<unsigned char> fbFlipped(fb.size());
                for (int y = 0; y < h; ++y) {
                    copy(fb.begin() + y * stride, fb.begin() + y * stride + stride, fbFlipped.end() - (y + 1) * stride);
                }

                stbi_write_png(ss.str().c_str(), w, h, 3, &fbFlipped[0], stride);
                return true;
            }
        }
        break;
    }

    return false;
}
//-----------------------------------------------------------------------------
bool ProcessGUIEvent(const SDL_Event& evt) {
    //return TwEventSDL(&evt, 1, 3) != 0;
    switch (evt.type) {
    case SDL_MOUSEBUTTONUP:
    case SDL_MOUSEBUTTONDOWN:
        return TwMouseButton(
            evt.type == SDL_MOUSEBUTTONUP ? TW_MOUSE_RELEASED : TW_MOUSE_PRESSED,
            static_cast<TwMouseButtonID>(evt.button.button)) != 0;

    case SDL_MOUSEMOTION:
        return TwMouseMotion(evt.motion.x, evt.motion.y) != 0;
    }

    return false;
}
//-----------------------------------------------------------------------------
void LoadGLProgram(GLenum target, const char* str) {
    glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, static_cast<GLsizei>(strlen(str)), str);
    const GLenum error = glGetError();
    const GLubyte* errorStr = glGetString(GL_PROGRAM_ERROR_STRING_ARB);

    if (error != GL_NO_ERROR) {
        cout << errorStr << endl;
        throw runtime_error("[ProgramString] Program source is invalid.");
    }

    if (errorStr[0] != 0) {
        cout << errorStr << endl;
    }
}
//-----------------------------------------------------------------------------
string GetGLProgramInfo(GLenum target) {
    ostringstream ss;
    GLint i, m;

    glGetProgramivARB(target, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &i);
    ss << "MAX_PROGRAM_LOCAL_PARAMETERS: " << i << endl;
    glGetProgramivARB(target, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &i);
    ss << "MAX_PROGRAM_ENV_PARAMETERS: " << i << endl;

    glGetProgramivARB(target, GL_PROGRAM_INSTRUCTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &m);
    ss << "PROGRAM_INSTRUCTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_ALU_INSTRUCTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB, &m);
    ss << "PROGRAM_ALU_INSTRUCTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_TEX_INSTRUCTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB, &m);
    ss << "PROGRAM_TEX_INSTRUCTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_TEX_INDIRECTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB, &m);
    ss << "PROGRAM_TEX_INDIRECTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_TEMPORARIES_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_TEMPORARIES_ARB, &m);
    ss << "PROGRAM_TEMPORARIES: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_PARAMETERS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_PARAMETERS_ARB, &m);
    ss << "PROGRAM_PARAMETERS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_ATTRIBS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_ATTRIBS_ARB, &m);
    ss << "PROGRAM_ATTRIBS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_ADDRESS_REGISTERS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB, &m);
    ss << "PROGRAM_ADDRESS_REGISTERS: " << i << " / " << m << endl;

    // NATIVE
    glGetProgramivARB(target, GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB, &m);
    ss << "PROGRAM_NATIVE_INSTRUCTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB, &m);
    ss << "PROGRAM_NATIVE_ALU_INSTRUCTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB, &m);
    ss << "PROGRAM_NATIVE_TEX_INSTRUCTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB, &m);
    ss << "PROGRAM_NATIVE_TEX_INDIRECTIONS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_TEMPORARIES_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB, &m);
    ss << "PROGRAM_NATIVE_TEMPORARIES: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_PARAMETERS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB, &m);
    ss << "PROGRAM_NATIVE_PARAMETERS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_ATTRIBS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB, &m);
    ss << "PROGRAM_NATIVE_ATTRIBS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB, &m);
    ss << "PROGRAM_NATIVE_ADDRESS_REGISTERS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_ATTRIB_COMPONENTS_NV, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV, &m);
    ss << "PROGRAM_ATTRIB_COMPONENTS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_PROGRAM_RESULT_COMPONENTS_NV, &i);
    glGetProgramivARB(target, GL_MAX_PROGRAM_RESULT_COMPONENTS_NV, &m);
    ss << "PROGRAM_RESULT_COMPONENTS: " << i << " / " << m << endl;

    glGetProgramivARB(target, GL_MAX_PROGRAM_IF_DEPTH_NV, &i);
    ss << "MAX_PROGRAM_IF_DEPTH: " << i << endl;
    glGetProgramivARB(target, GL_MAX_PROGRAM_LOOP_DEPTH_NV, &i);
    ss << "MAX_PROGRAM_LOOP_DEPTH: " << i << endl;
    glGetProgramivARB(target, GL_MAX_PROGRAM_CALL_DEPTH_NV, &i);
    ss << "MAX_PROGRAM_CALL_DEPTH: " << i << endl << endl;

    return ss.str();
}
//-----------------------------------------------------------------------------
CUmodule LoadPTXModule(const char* ptxCode) {
    CUmodule module;
    CUjit_option opt[5];
    void* optVal[5];
    vector<char> infoLog(1000);
    vector<char> errorLog(1000);

    opt[0] = CU_JIT_INFO_LOG_BUFFER;
    optVal[0] = &infoLog[0];
    opt[1] = CU_JIT_INFO_LOG_BUFFER_SIZE_BYTES;
    optVal[1] = reinterpret_cast<void*>(infoLog.size());
    opt[2] = CU_JIT_ERROR_LOG_BUFFER;
    optVal[2] = &errorLog[0];
    opt[3] = CU_JIT_ERROR_LOG_BUFFER_SIZE_BYTES;
    optVal[3] = reinterpret_cast<void*>(errorLog.size());

    if (cuModuleLoadDataEx(&module, ptxCode, 4, opt, optVal) != CUDA_SUCCESS) {
        if (infoLog[0] != 0) {
            cout << "INFO LOG:\n" << &infoLog[0] << endl;
        }
        if (errorLog[0] != 0) {
            cout << "ERROR LOG:\n" << &errorLog[0] << endl;
        }

        throw runtime_error("[LoadCUDAModule] PTX code is invalid.");
    }

    if (infoLog[0] != 0) {
        cout << "INFO LOG:\n" << &infoLog[0] << endl;
    }

    return module;
}
//-----------------------------------------------------------------------------
// Window
//-----------------------------------------------------------------------------
Window::Window(const char* title, int w, int h, int x, int y, Uint32 flags) {
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

    mWindow = SDL_CreateWindow(title, x, y, w, h, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | flags);
    if (!mWindow) {
        throw runtime_error("[Window::Window] SDL_CreateWindow failed.");
    }

    mWidth = w;
    mHeight = h;

    mLastTime = SDL_GetTicks();
    mLastSecond = mLastTime;
    mFrameCount = 0;
}
//-----------------------------------------------------------------------------
Window::~Window() {
    SDL_DestroyWindow(mWindow);
}
//-----------------------------------------------------------------------------
void Window::Swap() {
    mFrameCount++;

    const Uint32 now = SDL_GetTicks();
    const Uint32 dt = now - mLastTime;
    mLastTime = now;

    if (now - mLastSecond > 1000) {
        const float fps = static_cast<float>(mFrameCount) / (now - mLastSecond) * 1000.0f;

        string name(SDL_GetWindowTitle(mWindow));
        size_t b = name.find(" - ");
        const size_t e = name.find(" [");
        if (b != string::npos && e != string::npos) {
            b += 3;
            name = name.substr(b, e - b);
        }

        ostringstream os;
        os << "OpenGL Sample - " << name << " [fps: " << fps << ", ms: " <<
            (now - mLastSecond) / static_cast<float>(mFrameCount) << "]";
        SDL_SetWindowTitle(mWindow, os.str().c_str());

        mLastSecond = now;
        mFrameCount = 0;
    }

    SDL_GL_SwapWindow(mWindow);
}
//-----------------------------------------------------------------------------
// ArcBall
//-----------------------------------------------------------------------------
void ArcBall::Begin(int x, int y) {
    mDragging = true;
    mVecDown = ScreenToVector(static_cast<float>(x), static_cast<float>(y));
    mQuatDown = mQuatNow;
}
//-----------------------------------------------------------------------------
void ArcBall::End() {
    mDragging = false;
}
//-----------------------------------------------------------------------------
void ArcBall::Drag(int x, int y) {
    if (mDragging) {
        mVecNow = ScreenToVector(static_cast<float>(x), static_cast<float>(y));

        glm::vec3 p = glm::cross(mVecDown, mVecNow);

        if (glm::length(p) > 1e-5) {
            glm::quat q = glm::quat(glm::dot(mVecDown, mVecNow), p);
            mQuatNow = glm::normalize(q) * mQuatDown;
        }
        else {
            mQuatNow = glm::quat() * mQuatDown;
        }
    }
}
//-----------------------------------------------------------------------------
void ArcBall::SetWindowSize(int width, int height) {
    mWidth = width;
    mHeight = height;
}
//-----------------------------------------------------------------------------
glm::vec3 ArcBall::ScreenToVector(float screenX, float screenY) {
    glm::vec2 v;
    v.x = ((screenX / ((mWidth - 1) / 2)) - 1);
    v.y = -((screenY / ((mHeight - 1) / 2)) - 1);

    float len = glm::length(v);
    if (len > 1.0f)
        return glm::vec3(v / sqrt(len), 0);

    return glm::vec3(v, sqrt(1.0f - len));
}
//-----------------------------------------------------------------------------
